package se.thoka.twt.client.utils;

import java.io.Serializable;
import java.util.Date;

import com.google.gwt.user.client.rpc.IsSerializable;

/**
 * <p>
 * <code>GWTDateTime</code> represents a local date and time. It is implemented using a {@link GWTDate} and a {@link GWTTime} bean.
 * </p>
 * 
 * <p>
 * <code>GWTDateTime</code> performs no validation of its values.
 * </p>
 * 
 * @author Thobias Karlsson, Thoka
 * @since 9 jan 2010
 */
@SuppressWarnings("deprecation")
public class GWTDateTime implements IsSerializable, Serializable, SerializableType<String>, Comparable<GWTDateTime> {

	private static final long serialVersionUID = 79599328891928244L;
	private static final String NULL = "NULL";
	/**
	 * The date.
	 */
	private GWTDate date;
	/**
	 * The time.
	 */
	private GWTTime time;

	/**
	 * Creates a new instance of <code>GWTDateTime</code>.
	 */
	public GWTDateTime() {
		this(null, null);
	}

	/**
	 * Creates a new instance of {@link GWTDateTime} where time will be set to <code>00:00:00.000</code>
	 * 
	 * @param date The date
	 */
	public GWTDateTime(GWTDate date) {
		this(new GWTDateTime(date, new GWTTime(0, 0, 0, 0)));
	}

	/**
	 * Creates a new instance of <code>GWTDateTime</code>.
	 * 
	 * @param dateTime The date & time.
	 */
	public GWTDateTime(GWTDateTime dateTime) {
		this(new GWTDate(dateTime.getDate()), new GWTTime(dateTime.getTime()));
	}

	/**
	 * Creates a new instance of <code>GWTDateTime</code>.
	 * 
	 * @param dateTime A datetime. Must not be <code>null</code>.
	 */
	public GWTDateTime(Date dateTime) {
		this(new GWTDate(dateTime), new GWTTime(dateTime));
	}

	/**
	 * Creates a new instance of <code>GWTDateTime</code> representing a date.
	 * 
	 * @param year The year, e.g. 1985.
	 * @param monthOfYear The month of the year, e.g. 1 (representing January).
	 * @param dayOfMonth The day of the month, e.g. 12 (representing the 12th day of the month).
	 */
	public GWTDateTime(int year, int monthOfYear, int dayOfMonth) {
		this(new GWTDate(year, monthOfYear, dayOfMonth), null);
	}

	/**
	 * Creates a new instance of <code>GWTDateTime</code> representing a date.
	 * 
	 * @param year The year, e.g. 1985.
	 * @param monthOfYear The month of the year, e.g. 1 (representing January).
	 * @param dayOfMonth The day of the month, e.g. 12 (representing the 12th day of the month).
	 * @param hourOfDay The hour of day, a value between 0 and 23.
	 * @param minuteOfHour The minute of hour, a value between 0 and 59.
	 */
	public GWTDateTime(int year, int monthOfYear, int dayOfMonth, int hourOfDay, int minuteOfHour) {
		this(new GWTDate(year, monthOfYear, dayOfMonth), new GWTTime(hourOfDay, minuteOfHour, 0, 0));
	}

	/**
	 * Creates a new instance of <code>GWTDateTime</code> representing a date.
	 * 
	 * @param year The year, e.g. 1985.
	 * @param monthOfYear The month of the year, e.g. 1 (representing January).
	 * @param dayOfMonth The day of the month, e.g. 12 (representing the 12th day of the month).
	 * @param hourOfDay The hour of day, a value between 0 and 23.
	 * @param minuteOfHour The minute of hour, a value between 0 and 59.
	 * @param secondOfMinute The number of seconds in the minute, a value between 0 and 59.
	 * @param millisOfSecond The number of milliseconds in the second, a value between 0 and 999.
	 */
	public GWTDateTime(int year, int monthOfYear, int dayOfMonth, int hourOfDay, int minuteOfHour, int secondOfMinute, int millisOfSecond) {
		this(new GWTDate(year, monthOfYear, dayOfMonth), new GWTTime(hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond));
	}

//	/**
//	 * Creates a new instance of <code>GWTDateTime</code> representing a time without a date.
//	 * 
//	 * @param hourOfDay The hour of day, a value between 0 and 23.
//	 * @param minuteOfHour The minute of hour, a value between 0 and 59.
//	 * @param secondOfMinute The number of seconds in the minute, a value between 0 and 59.
//	 * @param millisOfSecond The number of milliseconds in the second, a value between 0 and 999.
//	 */
//	public GWTDateTime(int hourOfDay, int minuteOfHour, int secondOfMinute, int millisOfSecond) {
//		this(null, new GWTTime(hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond));
//	}

	/**
	 * Creates a new instance of <code>GWTDateTime</code>.
	 * 
	 * @param date The date.
	 * @param time The time.
	 */
	public GWTDateTime(GWTDate date, GWTTime time) {
		setDate(date);
		setTime(time);
	}

	/**
	 * Returns the date.
	 * 
	 * @return The date.
	 */
	public GWTDate getDate() {
		return date;
	}

	/**
	 * Returns the time of day.
	 * 
	 * @return The time of day.
	 */
	public GWTTime getTime() {
		return time;
	}

	/**
	 * Sets the date.
	 * 
	 * @param date The new date.
	 */
	public void setDate(GWTDate date) {
		this.date = date;
	}

	/**
	 * Sets the time of day.
	 * 
	 * @param time The new time of day.
	 */
	public void setTime(GWTTime time) {
		this.time = time;
	}

	/**
	 * Converts this <code>GWTDateTime</code> into a {@link java.util.Date}. This method uses deprecated functionality in {@link java.util.Date} that
	 * will still map correctly to the Javascript Date object.
	 * 
	 * @return A {@link java.util.Date}.
	 */
	public Date toDate() {
		Date result = new Date();
		if (getTime() != null) {
			result.setHours(getTime().getHourOfDay());
			result.setMinutes(getTime().getMinuteOfHour());
			result.setSeconds(getTime().getSecondOfMinute());
		}
		if (getDate() != null) {
			result.setYear(getDate().getYear() - 1900);
			result.setMonth(getDate().getMonthOfYear() - 1);
			result.setDate(getDate().getDayOfMonth());
		}
		return result;
	}

	@Override
	public String toString() {
		StringBuilder result = new StringBuilder();
		if (date != null) {
			result.append(date.toString());
			if (time != null) {
				result.append("T");
			}
		}
		if (time != null) {
			result.append(time.toString());
		}
		
		return result.toString();
	}

	@Override
	public boolean equals(Object obj) {
		return (obj != null && obj.hashCode() == hashCode());
	}

	@Override
	public int hashCode() {
		return (date != null ? date.hashCode() : 0) ^ (time != null ? time.hashCode() : 0);
	}

	/**
	 * Returns <code>true</code> if this <code>GWTDateTime</code> is strictly before the supplied <code>GWTDateTime</code>.
	 * 
	 * @param dateTime The <code>GWTDateTime</code> to compare against.
	 * @return <code>true</code> if this <code>GWTDateTime</code> is before <code>dateTime</code>.
	 */
	public boolean isBefore(GWTDateTime dateTime) {
		boolean result = false;
		if (!equals(dateTime)) {
			result = !isAfter(dateTime);
		}
		return result;
	}

	/**
	 * Returns <code>true</code> if this <code>GWTDateTime</code> is strictly after the supplied <code>GWTDateTime</code>.
	 * 
	 * @param dateTime The <code>GWTDateTime</code> to compare against.
	 * @return <code>true</code> if this <code>GWTDateTime</code> is after <code>dateTime</code>.
	 */
	public boolean isAfter(GWTDateTime dateTime) {
		boolean result = false;
		if (!equals(dateTime)) {
			result = getDate().isAfter(dateTime.getDate()) || (getDate().equals(dateTime.getDate()) && getTime().isAfter(dateTime.getTime()));
		}
		return result;
	}

	/**
	 * Compares this <code>GWTDateTime</code> with the supplied <code>GWTDateTime</code>.
	 * 
	 * @param dateTime The <code>GWTDateTime</code> to compare against.
	 * @return <code>0</code> if equal, <code>-1</code> if this <code>GWTdateTime</code> is before the supplied <code>GWTDateTime</code> and
	 *         <code>1</code> if this <code>GWTDateTime</code> is after the supplied <code>GWTDateTime</code>
	 */
	@Override
	public int compareTo(GWTDateTime dateTime) {
		int result = 0;
		if (isBefore(dateTime)) {
			result = -1;
		} else if (isAfter(dateTime)) {
			result = 1;
		}
		return result;
	}
	
	@Override
	public void readSerial(String serial) throws IllegalArgumentException, NullPointerException {
		this.date = null;
		this.time = null;
		
		String[] split = serial.split("T");
		if (!NULL.equals(split[0])) {
			GWTDate date = new GWTDate();
			date.readSerial(split[0]);
			this.date = date;
		}
		if (!NULL.equals(split[1])) {
			GWTTime time = new GWTTime();
			time.readSerial(split[1]);
			this.time = time;
		}
	}
	
	@Override
	public String writeSerial() {
		StringBuilder result = new StringBuilder();
		result.append(date != null ? date.writeSerial() : NULL);
		result.append('T');
		result.append(time != null ? time.writeSerial() : NULL);
		return result.toString();
	}
	
	public static GWTDateTime now() {
		return new GWTDateTime(new Date());
	}
}
